Suggest the best matching target for cargo run
authorRyan Quattlebaum <ryan.quattlebaum@icloud.com>
Mon, 14 Mar 2016 17:22:17 +0000 (13:22 -0400)
committerRyan Quattlebaum <ryan.quattlebaum@icloud.com>
Mon, 14 Mar 2016 17:26:29 +0000 (13:26 -0400)
Targets passed to cargo compile are validated against the package. If
the target exists, it is compiled. If not, cargo will bail and offer a
suggested target name if there is a close match.

The tests create and build/run binaries and examples using filenames
that are close (or not so close) to the target names to verify that
close matching names are suggested to the user.

src/cargo/core/package.rs
src/cargo/ops/cargo_compile.rs
tests/test_cargo_compile.rs
tests/test_cargo_run.rs

index c8362ffa286bfb32417e7d68590f08b63ebe39c1..703fcff83ca0d1af28023970a82c73f3073a7df2 100644 (file)
@@ -6,10 +6,10 @@ use std::path::{Path, PathBuf};
 
 use semver::Version;
 
-use core::{Dependency, Manifest, PackageId, SourceId, Target};
+use core::{Dependency, Manifest, PackageId, SourceId, Target, TargetKind};
 use core::{Summary, Metadata, SourceMap};
 use ops;
-use util::{CargoResult, Config, LazyCell, ChainError, internal, human};
+use util::{CargoResult, Config, LazyCell, ChainError, internal, human, lev_distance};
 use rustc_serialize::{Encoder,Encodable};
 
 /// Information about a package that is available somewhere in the file system.
@@ -89,6 +89,15 @@ impl Package {
     pub fn generate_metadata(&self) -> Metadata {
         self.package_id().generate_metadata()
     }
+
+    pub fn find_closest_target(&self, target: &str, kind: TargetKind) -> Option<&Target> {
+        let targets = self.targets();
+
+        let matches = targets.iter().filter(|t| *t.kind() == kind)
+                                    .map(|t| (lev_distance(target, t.name()), t))
+                                    .filter(|&(d, _)| d < 4);
+        matches.min_by_key(|t| t.0).map(|t| t.1)
+    }
 }
 
 impl fmt::Display for Package {
index ad82d4f773e002888ad7cd2fae307db68e45722b..d5eb7ec09f2be8d3c3b0cef5aa714dd4febb25a7 100644 (file)
@@ -138,6 +138,25 @@ pub fn resolve_dependencies<'a>(root_package: &Package,
     Ok((packages, resolved_with_overrides))
 }
 
+fn validate_target(package: &Package,
+                   name: &str,
+                   kind: TargetKind,
+                   kind_str: &str) -> CargoResult<()> {
+    let target = package.targets().iter().find(|t: &&Target| {
+        t.name() == name && *t.kind() == TargetKind::Bin
+    });
+    if target.is_none() {
+        let suggestion = package.find_closest_target(name, kind);
+        match suggestion {
+            Some(s) => bail!("no {} target named `{}`\n\nDid you mean `{}`?",
+                             kind_str, name, s.name()),
+            None => bail!("no {} target named `{}`", kind_str, name),
+        }
+    }
+
+    Ok(())
+}
+
 pub fn compile_pkg<'a>(root_package: &Package,
                        source: Option<Box<Source + 'a>>,
                        options: &CompileOptions<'a>)
@@ -157,6 +176,16 @@ pub fn compile_pkg<'a>(root_package: &Package,
         bail!("jobs must be at least 1")
     }
 
+    if let CompileFilter::Only{bins, examples, ..} = *filter {
+        for bin in bins {
+            try!(validate_target(root_package, bin, TargetKind::Bin, "bin"));
+        }
+
+        for example in examples {
+            try!(validate_target(root_package, example, TargetKind::Example, "example"));
+        }
+    }
+
     let (packages, resolve_with_overrides) = {
         try!(resolve_dependencies(root_package, config, source, features,
                                   no_default_features))
index 731546e6372ea5cfb358a315cfa9f04d6bc7ad94..35652a1964f650f757072c091ad220d0b2a9ddc8 100644 (file)
@@ -621,6 +621,44 @@ version required: *
 "#, error = ERROR, proj_dir = p.url())));
 });
 
+test!(cargo_compile_with_filename{
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+        "#)
+        .file("src/lib.rs", "")
+        .file("src/bin/a.rs", r#"
+            extern crate foo;
+            fn main() { println!("hello a.rs"); }
+        "#)
+        .file("examples/a.rs", r#"
+            fn main() { println!("example"); }
+        "#);
+
+    assert_that(p.cargo_process("build").arg("--bin").arg("bin.rs"),
+                execs().with_status(101).with_stderr(&format!("\
+{error} no bin target named `bin.rs`", error = ERROR)));
+
+    assert_that(p.cargo_process("build").arg("--bin").arg("a.rs"),
+                execs().with_status(101).with_stderr(&format!("\
+{error} no bin target named `a.rs`
+
+Did you mean `a`?", error = ERROR)));
+
+    assert_that(p.cargo_process("build").arg("--example").arg("example.rs"),
+                execs().with_status(101).with_stderr(&format!("\
+{error} no example target named `example.rs`", error = ERROR)));
+
+    assert_that(p.cargo_process("build").arg("--example").arg("a.rs"),
+                execs().with_status(101).with_stderr(&format!("\
+{error} no example target named `a.rs`
+
+Did you mean `a`?", error = ERROR)));
+});
+
 test!(compile_path_dep_then_change_version {
     let p = project("foo")
         .file("Cargo.toml", r#"
index 8dffc242ffa31704c3b0aa560b94feac8ab2bb16..b71073dc9359bcaf099c48a3d113beae61f15ddb 100644 (file)
@@ -234,6 +234,44 @@ example
         sep = SEP)));
 });
 
+test!(run_with_filename {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.0.1"
+            authors = []
+        "#)
+        .file("src/lib.rs", "")
+        .file("src/bin/a.rs", r#"
+            extern crate foo;
+            fn main() { println!("hello a.rs"); }
+        "#)
+        .file("examples/a.rs", r#"
+            fn main() { println!("example"); }
+        "#);
+
+    assert_that(p.cargo_process("run").arg("--bin").arg("bin.rs"),
+                execs().with_status(101).with_stderr(&format!("\
+{error} no bin target named `bin.rs`", error = ERROR)));
+
+    assert_that(p.cargo_process("run").arg("--bin").arg("a.rs"),
+                execs().with_status(101).with_stderr(&format!("\
+{error} no bin target named `a.rs`
+
+Did you mean `a`?", error = ERROR)));
+
+    assert_that(p.cargo_process("run").arg("--example").arg("example.rs"),
+                execs().with_status(101).with_stderr(&format!("\
+{error} no example target named `example.rs`", error = ERROR)));
+
+    assert_that(p.cargo_process("run").arg("--example").arg("a.rs"),
+                execs().with_status(101).with_stderr(&format!("\
+{error} no example target named `a.rs`
+
+Did you mean `a`?", error = ERROR)));
+});
+
 test!(either_name_or_example {
     let p = project("foo")
         .file("Cargo.toml", r#"